﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
using Math = System.Math;

using Texture2D = Microsoft.Xna.Framework.Graphics.Texture2D;
using Color = System.Drawing.Color;

using RFEngine.Graphics.Fonts;
using RFEngine.Storage;

namespace RFEngine.Graphics
{
    public sealed class Font : IDisposable
    {
        #region Fields

        private System.Drawing.Bitmap _GlobalBitmap;
        private System.Drawing.Graphics _GlobalGraphics;

        private Texture2D _Texture;
        private FontDescription _FontDescription;
        private int _PointSize;

        #endregion

        #region Properties

        internal FontDescription FontDescription
        {
            get { return _FontDescription; }
        }

        internal Texture2D Texture
        {
            get { return _Texture; }
        }

        /// <summary>
        /// Gets the size of the font, when loaded.
        /// </summary>
        public int FontSize
        {
            get { return _PointSize; }
        }

        #endregion

        #region Methods

        #region Constructor(s)

        /// <summary>
        /// Constructs a new instance of this object.
        /// </summary>
        /// <param name="fontLocation">a string path to the location of the font file to use.</param>
        /// <param name="fontSize">The font size to use when rendering this object</param>
        public Font(string fontLocation, int fontSize)
        {
            if (String.IsNullOrEmpty(fontLocation))
                throw new ArgumentException("fontLocation is null or empty.", "fontLocation");

            if (!StorageContainer.FileExists(fontLocation))
                throw new Microsoft.Xna.Framework.Content.ContentLoadException("fontLocation does not exist");

            if (fontLocation.ToLower().EndsWith(".ttf") || fontLocation.ToLower().EndsWith(".otf"))
            {
                _Texture = null;
                _GlobalBitmap = new Bitmap(1, 1);
                _GlobalGraphics = System.Drawing.Graphics.FromImage(_GlobalBitmap);

                PrivateFontCollection fontCollection = new PrivateFontCollection();
                fontCollection.AddFontFile(fontLocation);

                int minChar = 32;
                int maxChar = 127;

                List<Bitmap> bitmaps = new List<Bitmap>();
                List<int> xPositions = new List<int>();
                List<int> yPositions = new List<int>();

                const int padding = 8;

                int width = padding;
                int height = padding;
                int lineWidth = padding;
                int lineHeight = padding;
                int count = 0;

                _FontDescription = new FontDescription();
                System.Drawing.Font drawingFont = new System.Drawing.Font(fontCollection.Families[0], fontSize, FontStyle.Bold);
                for (char ch = (char)minChar; ch < maxChar; ch++)
                {
                    _FontDescription.CharacterInfo[(int)ch] = new CharacterInfo(ch);
                    Bitmap bitmap = RasterizeCharacter(ch, drawingFont);

                    bitmaps.Add(RasterizeCharacter(ch, drawingFont));

                    xPositions.Add(lineWidth);
                    yPositions.Add(height);

                    lineWidth += bitmap.Width + padding;
                    lineHeight = System.Math.Max(lineHeight, bitmap.Height + padding);

                    if ((++count == 16) || (ch == maxChar - 1))
                    {
                        width = System.Math.Max(width, lineWidth);
                        height += lineHeight;
                        lineWidth = padding;
                        lineHeight = padding;
                        count = 0;
                    }
                }

                using (Bitmap bitmap = new Bitmap(width, height,
                                                                  System.Drawing.Imaging.PixelFormat.Format32bppArgb))
                {
                    using (System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(bitmap))
                    {
                        graphics.Clear(System.Drawing.Color.Transparent);
                        graphics.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;

                        for (int i = 0; i < bitmaps.Count; i++)
                        {
                            _FontDescription.CharacterInfo[minChar + i].TopTextureCoordinate = (float)System.Math.Round((float)(yPositions[i] / (float)bitmap.Height), 4);
                            _FontDescription.CharacterInfo[minChar + i].BottomTextureCoordinate = (float)System.Math.Round((float)((yPositions[i] + bitmaps[i].Height) / (float)bitmap.Height), 4);
                            _FontDescription.CharacterInfo[minChar + i].LeftTextureCoordinate = (float)System.Math.Round((float)(xPositions[i] / (float)bitmap.Width), 4);
                            _FontDescription.CharacterInfo[minChar + i].RightTextureCoordinate = (float)System.Math.Round((float)((xPositions[i] + bitmaps[i].Width) / (float)bitmap.Width), 4);
                            _FontDescription.CharacterInfo[minChar + i].XScale = (float)System.Math.Round((float)(bitmaps[i].Width / (float)bitmaps[i].Height), 4);
                            _FontDescription.CharacterInfo[minChar + i].Spaceing = 8;

                            graphics.DrawImage(bitmaps[i], xPositions[i],
                                                           yPositions[i]);
                        }

                        graphics.Flush();
                    }
                    using (System.IO.MemoryStream s = new MemoryStream())
                    {
                        bitmap.Save(s, System.Drawing.Imaging.ImageFormat.Bmp);
                        s.Seek(0, SeekOrigin.Begin);
                        _Texture = Texture2D.FromFile(RFEngine.EngineServices.GraphicsDevice, s);
                    }
                }
            }

            _PointSize = fontSize;
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Helper for rendering out a single font chr into a System.Drawing bitmap.
        /// </summary>
        private Bitmap RasterizeCharacter(char ch, System.Drawing.Font font)
        {
            string text = ch.ToString();

            SizeF size = _GlobalGraphics.MeasureString(text, font);

            int width = (int)System.Math.Ceiling(size.Width);
            int height = (int)System.Math.Ceiling(size.Height);

            Bitmap bitmap = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

            using (System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(bitmap))
            {
                graphics.TextRenderingHint =
                    System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;

                graphics.Clear(Color.Transparent);

                using (Brush brush = new SolidBrush(Color.White))
                {
                    using (StringFormat format = new StringFormat())
                    {
                        format.Alignment = StringAlignment.Near;
                        format.Trimming = StringTrimming.Character;
                        format.LineAlignment = StringAlignment.Near;

                        graphics.DrawString(text, font, brush, 0, 0, format);
                    }
                }

                graphics.Flush();
            }

            return CropCharacter(bitmap);
        }

        /// <summary>
        /// Helper for cropping ununsed space from the sides of a bitmap.
        /// </summary>
        private Bitmap CropCharacter(Bitmap bitmap)
        {
            int cropLeft = 0;
            int cropRight = bitmap.Width - 1;

            while ((cropLeft < cropRight) && (BitmapIsEmpty(bitmap, cropLeft)))
                cropLeft++;

            while ((cropRight > cropLeft) && (BitmapIsEmpty(bitmap, cropRight)))
                cropRight--;

            if (cropLeft == cropRight)
                return bitmap;

            cropLeft = System.Math.Max(cropLeft - 1, 0);
            cropRight = System.Math.Min(cropRight + 1, bitmap.Width - 1);

            int width = cropRight - cropLeft + 1;

            Bitmap croppedBitmap = new Bitmap(width, bitmap.Height, bitmap.PixelFormat);

            using (System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(croppedBitmap))
            {
                graphics.Clear(Color.Transparent);
                graphics.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;

                graphics.DrawImage(bitmap, 0, 0, new Rectangle(cropLeft, 0, bitmap.Height, bitmap.Height), GraphicsUnit.Pixel);

                graphics.Flush();
            }

            bitmap.Dispose();

            return croppedBitmap;
        }

        /// <summary>
        /// Helper for testing whether a column of a bitmap is entirely empty.
        /// </summary>
        private bool BitmapIsEmpty(Bitmap bitmap, int x)
        {
            for (int y = 0; y < bitmap.Height; y++)
            {
                if (bitmap.GetPixel(x, y).A != 0)
                    return false;
            }

            return true;
        }

        #endregion

        #region Interface Members

        void IDisposable.Dispose()
        {
            _Texture.Dispose();

            GC.SuppressFinalize(this);
        }

        #endregion

        #endregion
    }
}
